package com.uobgroup.ews.uam.core.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.uobgroup.ews.uam.core.common.SecurityGroupUtils;
import com.uobgroup.ews.uam.core.dao.ProfileDao;
import com.uobgroup.ews.uam.core.dao.ReferenceDao;
import com.uobgroup.ews.uam.core.domain.CombinationACL;
import com.uobgroup.ews.uam.core.domain.CombinationMatrix;
import com.uobgroup.ews.uam.core.domain.Profile;
import com.uobgroup.ews.uam.core.domain.Reference;
import com.uobgroup.ews.uam.core.domain.SecurityGroup;
import com.uobgroup.ews.uam.core.domain.Segment;
import com.uobgroup.ews.uam.core.domain.UAMRole;
import com.uobgroup.ews.uam.core.model.CombinationACLParamsName;
import com.uobgroup.ews.uam.core.model.ProfileInfo;
import com.uobgroup.ews.uam.core.model.ProfileInfoList;
import com.uobgroup.ews.uam.core.service.ldap.LDSService;

@Service
@Transactional
public class ProfileServiceImpl implements ProfileService {

   @Autowired
   ProfileDao profileDao;

   @Autowired
   ReferenceDao refDao;

   @Autowired
   UAMRoleService roleService;
   
   @Autowired
   ReferenceService referenceService;
   
   @Autowired
   CombinationMatrixService combinationMatrixService;
   
   @Autowired
   LDSService ldsService;

   public long countProfiles() {
      return profileDao.count();
   }

   public void addNewProfile(Profile profile) {
      profile.setSecurityGroup(SecurityGroupUtils.getSecurityGroup(profile));
      profileDao.save(profile);
      assignProfileGroupToRoles(profile);
      assignProfileGroupToCombinations(profile);
      assignProfileGroupToSegments(profile);
      assignProfileGroupToAccessToStaff(profile);
   }
   
   private void assignProfileGroupToAccessToStaff(Profile profile){
      ldsService.assignChildGroupToParent(profile.getAccessToStaff().getSecurityGroup(), profile.getSecurityGroup());
   }
   
   private void assignProfileGroupToSegments(Profile profile){
      for(Reference segmentReference : profile.getSegments()){
         ldsService.assignChildGroupToParent(segmentReference.getSegment().getSecurityGroup(),
               profile.getSecurityGroup());
      }
   }

   private void assignProfileGroupToRoles(Profile profile) {
      for (UAMRole role : profile.getRoles()) {
         ldsService.assignChildGroupToParent(role.getSecurityGroup(), profile.getSecurityGroup());
      }
   }
   
   private void assignProfileGroupToCombinations(Profile profile){
      for(CombinationMatrix combination : profile.getCombinationMatrices()){
         if(combination.getAccessControls() != null){
            for(CombinationACL acl : combination.getAccessControls()){
               ldsService.assignChildGroupToParent(acl.getSecurityGroup(), profile.getSecurityGroup());
            }
         }
      }
   }

   public Profile updateProfile(Profile profile) {
      Profile originalProfile = profileDao.findOne(profile.getId());
      SecurityGroup originalSecGroup = new SecurityGroup();
      originalSecGroup.setDisplayName(originalProfile.getSecurityGroup().getDisplayName());
      originalSecGroup.setDN(originalProfile.getSecurityGroup().getDN());
      updateRelatedInfo(originalProfile);
      
      List<SecurityGroup> newAssignedGroups = new ArrayList<SecurityGroup>();
      List<SecurityGroup> unassignedGroups = new ArrayList<SecurityGroup>();
      
      if(profile.getAccessToStaff().getId() != originalProfile.getAccessToStaff().getId()){
         unassignedGroups.add(originalProfile.getAccessToStaff().getSecurityGroup());
         newAssignedGroups.add(profile.getAccessToStaff().getSecurityGroup());
      }
      
      //Find the newly added group and the removed group
      List<UAMRole> removedRoles = new ArrayList<UAMRole>();
      removedRoles.addAll(originalProfile.getRoles());
      removedRoles.removeAll(profile.getRoles());
      for(UAMRole role : removedRoles){
         unassignedGroups.add(role.getSecurityGroup());
      }
      List<UAMRole> addedRoles = new ArrayList<UAMRole>();
      addedRoles.addAll(profile.getRoles());
      addedRoles.removeAll(originalProfile.getRoles());
      for(UAMRole role : addedRoles){
         newAssignedGroups.add(role.getSecurityGroup());
      }
      
      List<Reference> removedSegments = new ArrayList<Reference>();
      removedSegments.addAll(originalProfile.getSegments());
      removedSegments.removeAll(profile.getSegments());
      for(Reference segmentReference : removedSegments){
         unassignedGroups.add(segmentReference.getSegment().getSecurityGroup());
      }
      List<Reference> addedSegments = new ArrayList<Reference>();
      addedSegments.addAll(profile.getSegments());
      addedSegments.removeAll(originalProfile.getSegments());
      for(Reference segmentReference : addedSegments){
         newAssignedGroups.add(segmentReference.getSegment().getSecurityGroup());
      }
      
      List<CombinationACL> removedCombinationACLs = new ArrayList<CombinationACL>();
      for(CombinationMatrix matrix : originalProfile.getCombinationMatrices()){
         removedCombinationACLs.addAll(matrix.getAccessControls());
      }
      for(CombinationMatrix matrix : profile.getCombinationMatrices()){
         removedCombinationACLs.removeAll(matrix.getAccessControls());
      }
      for(CombinationACL acl : removedCombinationACLs){
         unassignedGroups.add(acl.getSecurityGroup());
      }
      List<CombinationACL> addedCombinationACLs = new ArrayList<CombinationACL>();
      for(CombinationMatrix matrix : profile.getCombinationMatrices()){
         addedCombinationACLs.addAll(matrix.getAccessControls());
      }
      for(CombinationMatrix matrix : originalProfile.getCombinationMatrices()){
         addedCombinationACLs.removeAll(matrix.getAccessControls());
      }
      for(CombinationACL acl : addedCombinationACLs){
         newAssignedGroups.add(acl.getSecurityGroup());
      }
      
      profile = profileDao.update(profile);
      
      for(SecurityGroup securityGroup : unassignedGroups){
         ldsService.unassignChildGroupFromParent(securityGroup, originalSecGroup);
      }
      for(SecurityGroup securityGroup : newAssignedGroups){
         ldsService.assignChildGroupToParent(securityGroup, originalSecGroup);
      }
      
      return profile;
   }
   
   
   private void updateRelatedInfo(Profile profile){
      List<UAMRole> roles = profile.getRoles();
      if(roles != null && roles.size() > 0){
         for(int i = 0; i < roles.size(); i++){
            UAMRole role = roles.get(i);
            role = roleService.findUAMRole(role.getId());
            roles.set(i, role);
         }
      }
      
      List<Reference> segments = profile.getSegments();
      if(segments != null && segments.size() > 0){
         for(int i = 0; i < segments.size(); i++){
            Reference segment = segments.get(i);
            segment = referenceService.findReference(segment.getId());
            segments.set(i, segment);
         }
      }
      
      List<CombinationMatrix> matrices = profile.getCombinationMatrices();
      if(matrices != null && matrices.size() > 0){
         for(int i = 0; i < matrices.size(); i++){
            CombinationMatrix matrix = matrices.get(i);
            matrix = combinationMatrixService.findbyId(matrix.getId());
            matrices.set(i, matrix);
         }
      }
   }

   public List<Profile> findProfiles(int firstResult, int maxResult) {
      return profileDao.findAll(firstResult, maxResult);
   }

   @Override
   public Profile findProfileById(long id) {
      return profileDao.findOne(id);
   }

   @Override
   public List<Profile> findAllProfiles() {
      return profileDao.findAll();
   }

   @Override
   public void deleteProfile(Profile profile) {
      profileDao.delete(profile);
   }

   @Override
   public boolean isProfileInUse(Long id) {
      return profileDao.isProfileInUse(id);
   }

   @Override
   public List<Profile> findAdminRole() {
      return profileDao.findAdminRole();
   }

   @Override
   public List<Profile> findNonAdminRole() {
      return profileDao.findNonAdminRole();
   }

   @Override
   public void updateUserIdAndDelete(Long id, String userId) {
      profileDao.updateUserIdAndDelete(Profile.class, id, userId);
   }

   /*
    * Phuong Tran
    */
   @Override
   public ProfileInfoList getProfileInfoList(List<String> segmentNames, HashMap<String, List<String>> combiParams) {
      List<ProfileInfo> lstProfileInfo = new ArrayList<ProfileInfo>();
      List<Segment> lstSegment = getSegmentFromName(segmentNames);
      Iterator<Segment> itSegment = lstSegment.iterator();
      while (itSegment.hasNext()) {
         Segment segment = itSegment.next();
         List<Profile> lstProfile = getProfileListContainSegment(segment);
         if (lstProfile != null) {
            List<Profile> lstFiltered = filterProfileBaseOnCombiParams(lstProfile, combiParams);
            buildProfileInfoList(lstProfileInfo, lstFiltered);
         }
      }
      return new ProfileInfoList(lstProfileInfo);
   }

   private List<Segment> getSegmentFromName(List<String> segmentNames) {
      List<Segment> lstSegment = new ArrayList<Segment>();
      Iterator<String> itSegName = segmentNames.iterator();
      while (itSegName.hasNext()) {
         String segName = itSegName.next();
         List<Reference> segReflst = refDao.findSegmentReferencesByName(segName);
         if (segReflst != null) {
            Iterator<Reference> itRefSeg = segReflst.iterator();
            while (itRefSeg.hasNext()) {
               Reference refSeg = itRefSeg.next();
               Segment seg = refDao.findSegmentByRefId(refSeg.getId());
               lstSegment.add(seg);
            }
         }
      }
      return lstSegment;
   }

   private List<Profile> getProfileListContainSegment(Segment seg) {
      List<Profile> lstProfile = profileDao.getProfileLstContainSegment(seg);
      return lstProfile;
   }

   private List<Profile> filterProfileBaseOnCombiParams(List<Profile> lstRawProfile,
         HashMap<String, List<String>> combiParams) {
      List<Profile> lstFiltered = new ArrayList<Profile>();
      Iterator<Profile> itProfile = lstRawProfile.iterator();
      while (itProfile.hasNext()) {
         Profile rawProfile = itProfile.next();
         List<CombinationMatrix> lstCombi = rawProfile.getCombinationMatrices();
         if (isCombinationDataMatch(lstCombi, combiParams)) {
            lstFiltered.add(rawProfile);
         }
      }
      return lstFiltered;
   }

   /*
    * Phuong Tran
    */
   public boolean isCombinationDataMatch(List<CombinationMatrix> combiList, HashMap<String, List<String>> params) {
      boolean result = false;
      List<String> countryList = params.get(CombinationACLParamsName.COUNTRY);
      List<String> docTypeList = params.get(CombinationACLParamsName.DOC_TYPE);
      List<String> processingCenterList = params.get(CombinationACLParamsName.PROCESSING_CENTER);
      List<String> transactionTypeList = params.get(CombinationACLParamsName.TRANSACTION_TYPE);
      List<String> productTypeList = params.get(CombinationACLParamsName.PRODUCT_TYPE);
      List<String> userLocationList = params.get(CombinationACLParamsName.USER_LOCATION);

      Iterator<CombinationMatrix> itRaw = combiList.iterator();
      while (itRaw.hasNext()) {
         CombinationMatrix combiRaw = itRaw.next();
         if (isDataMatch(countryList, combiRaw.getCountry()) && isDataMatch(docTypeList, combiRaw.getDocumentType())
               && isDataMatch(processingCenterList, combiRaw.getProcessingCentre())
               && isDataMatch(transactionTypeList, combiRaw.getTransactionType())
               && isDataMatch(productTypeList, combiRaw.getProductType())
               && isDataMatch(userLocationList, combiRaw.getUserLocation())) {
            result = true;
            break;
         }
      }

      return result;
   }

   private boolean isDataMatch(List<String> searchValues, Reference ref) {
      boolean ret = false;
      if (ref == null) {
         ret = true;
      }
      else {
         if (searchValues == null) {
            ret = true;
         }
         else {
            if (searchValues.contains(ref.getName())) {
               ret = true;
            }
         }
      }
      return ret;
   }

   private void buildProfileInfoList(List<ProfileInfo> lstResult, List<Profile> lstProfile) {
      Iterator<Profile> itProfile = lstProfile.iterator();
      while (itProfile.hasNext()) {
         Profile profile = itProfile.next();
         lstResult.add(new ProfileInfo(profile.getName()));
      }
   }
   /**
    * @author dongn
    */
   @Override
   public long countProfilesThatHasSegmentReference(Reference segment) {
      return profileDao.countProfilesThatHasSegmentReference(segment);
   }
}
